package com.lixinwen.gdgame.game.room;

import com.google.gson.Gson;
import com.lixinwen.gdgame.game.entities.Hand;
import com.lixinwen.gdgame.game.entities.enums.GameStage;
import com.lixinwen.gdgame.game.entities.enums.HandType;
import com.lixinwen.gdgame.game.msg.Poster;
import com.lixinwen.gdgame.game.util.Picker;
import com.lixinwen.gdgame.vo.jo.actJO;
import com.lixinwen.gdgame.vo.jo.allOverJO;
import com.lixinwen.gdgame.vo.jo.infoJO;
import com.lixinwen.gdgame.vo.jo.overJO;
import com.lixinwen.gdgame.vo.res.AIRes;
import com.lixinwen.gdgame.vo.res.HumanRes;
import com.lixinwen.gdgame.vo.res.ReadyReq;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Value;
import org.yeauty.pojo.Session;

import java.util.*;

public class RoomContext {

    @Value("${game.count-down}")
    public static int timeCountDown;

    private Session[] players = new Session[4]; //四家session

    private Set<Session> all = new HashSet<>(); // 包括观战

    private Integer curIndex; //谁出牌
    private Integer curMaxIndex; // 当前最大出牌者
    private Integer curMaxLevel; // 当前最大牌等级
    private HandType curMaxType; //  最大牌型
    private Integer[] rank; // 四家等级
    private Integer curRank; // 当前等级
    private ArrayList<Integer>[] cards; // 四家手牌
    private int[] rest;

    private int[] finResult; //完牌顺序

    private HashMap<Integer,Hand[]> actionList;

    private int passCnt;

    Timer timer = new Timer();
    Random random = new Random();

    private GameStage gameStage;
    private final ArrayList<int[]> tributeCard = new ArrayList<>();
    private final ArrayList<int[]> backCard = new ArrayList<>();
    private final HashMap<Integer,Integer> mp = new HashMap<>();
    private int triSize = 0;
    private boolean[] ready = new boolean[4];
    private int fIndex = 0;
    private Integer upStage;
    private int[] Afailure;




    /**
     * 外部处理
     */
    public void getReady(ReadyReq req){
        this.ready[req.getIndex()] = req.getIsReady();
        for(int i=0;i<4;i++){
            if(!this.ready[i])return;
        }
        if(this.curRank==null){
            gameBegin();
        }
        else{
            epiBegin();
        }
    }
    public void takeSeat(Session session,int seat) {
        if(players[seat]!=null){
            Poster.sendMsg(session,"这个位置有人了");
            return ;
        }
        players[seat] = session;
        all.add(session);
    }
    public void changeSeat(Session session,int seat){
        if(players[seat]!=null){
            Poster.sendMsg(session,"这个位置有人了");
            return ;
        }
        for(int i=0;i<4;i++){
            if(session == players[i]){
                players[i] = null;
            }
        }
        players[seat] = session;
    }


    /**
     * 一级函数入口，直接响应msg
     */
    /**
     *  游戏开始入口，在满人后调用
     *  初始化全局变量
     *  直接选择一个人开始出牌
     */
    public void gameBegin(){
        curIndex = random.nextInt() % 4;
        upStage = curIndex%2;
        Afailure = new int[]{0,0};
        rank = new Integer[]{2,2,2,2};
        curRank = 2;
        epiBegin();
    }

    /**
     *  游戏小局开始入口
     *  发牌
     *  判断进贡
     *  若是直接调用相关函数
     *  若否开始出牌
     */
    public void epiBegin(){
        curMaxIndex = null;
        curMaxLevel = null;
        curMaxType = null;
        cards = initCards(); // 四家手牌
        rest = new int[]{27,27,27,27};
        passCnt = 0;
        fIndex = 0;

        if(finResult==null){
            finResult = new int[4];
            toPlay(curIndex);
        }
        else{
            if((finResult[2]+finResult[3])%2==0){
                triSize = 2;
                toTribute(finResult[2]);
                toTribute(finResult[3]);
            }else{
                triSize = 1;
                toTribute(finResult[3]);
            }
        }
    }

    private void getCard(int index,int card){
        this.cards[index].add(card);
    }

    /**
     *
     * @param hand 回贡手牌
     * @param index 来自谁
     */
    private void getBack(Hand hand,int index){
        assert hand.getCards().length == 1;
        playAHand(hand,index);
        getCard(mp.get(index),hand.getCards()[0]);
        backCard.add(new int[]{hand.getCards()[0],index});
        if(backCard.size()==triSize){
            this.gameStage = GameStage.PLAY;
            int first = getFirstPlayer();
            toPlay(first);
        }
    }

    private int getFirstPlayer() {
        if(triSize==1){
            return tributeCard.get(0)[1];
        }
        else{
            return tributeCard.get(1)[1];
        }
    }

    private void getBigger(int index,ArrayList<int[]> tmp){
        tmp.sort((o1,o2)->o1[0]-o2[0]);
        this.cards[index].add(tmp.get(tmp.size()-1)[0]);
        mp.put(index,tmp.get(tmp.size()-1)[1]);
    }

    private void getSmaller(int index,ArrayList<int[]> tmp){
        tmp.sort((o1,o2)->o1[0]-o2[0]);
        this.cards[index].add(tmp.get(0)[0]);
        mp.put(index,tmp.get(0)[1]);

    }
    private void getTribute(Hand hand,int index){
        assert hand.getCards().length == 1;
        tributeCard.add(new int[]{hand.getCards()[0],index});
        playAHand(hand,index);

        if(tributeCard.size()==triSize){
            gameStage = GameStage.BACK;
            if(triSize==2){
                mp.clear();
                getBigger(finResult[0],tributeCard);
                getSmaller(finResult[1],tributeCard);
                toBack(finResult[0]);
                toBack(finResult[1]);
            }else{
                mp.clear();
                getBigger(finResult[0],tributeCard);
                toBack(finResult[0]);
            }
        }
    }
    private void toBack(int index){
        Gson gson = new Gson();
        String json = gson.toJson(new actJO("act",
                index,getHandCards(index),
                getBackActionList(index),
                curRank,
                rest,
                curMaxType.type,
                curMaxLevel,
                curMaxIndex
                ));
        players[index].sendText(json);
    }

    private Hand[] getBackActionList(int index) {
        ArrayList<Hand> ans = new ArrayList<>();
        Picker.pickBack(cards[index],ans,curRank);
        Hand[] ret = new Hand[ans.size()];
        return ans.toArray(ret);
    }

    private Hand[] getTributeActionList(int index) {
        ArrayList<Hand> ans = new ArrayList<>();
        Picker.pickTribute(cards[index],ans,curRank);
        Hand[] ret = new Hand[ans.size()];
        return ans.toArray(ret);
    }

    private void toTribute(int index) {
        Gson gson = new Gson();
        String json = gson.toJson(new actJO("act",
                index,getHandCards(index),
                getTributeActionList(index),
                curRank,
                rest,
                curMaxType.type,
                curMaxLevel,
                curMaxIndex
        ));
        players[index].sendText(json);
    }

    private ArrayList<Integer>[] initCards() {
        ArrayList<Integer> allCards = new ArrayList<>();
        for(int i=0;i<52;i++){
            allCards.add(i);
            allCards.add(i);
        }
        allCards.add(1000);
        allCards.add(1000);
        allCards.add(2000);
        allCards.add(2000);
        Collections.shuffle(allCards);
        Collections.shuffle(allCards);
        int index = 0;
        ArrayList<Integer>[] ans = new ArrayList[4];
        for(int i=0;i<4;i++){
            ans[i] = new ArrayList<Integer>();
        }
        for(Integer t:allCards){
            ans[index].add(t);
            index = (index + 1) % 4;
        }
        return ans;
    }

    /**
     * 根据index选择一个人发送出牌的提示
     * 启动计时器
     * @param index
     */
    public void toPlay(int index){
        timer.cancel();

        if(rest[index]==0){
            index = (index + 2) % 4;
        }
        Gson gson = new Gson();
        String json = gson.toJson(new actJO(this));
        players[index].sendText(json);

        waitForPlay();


    }

    /**
     * 调用此函数之前json解析
     * 收到的结果在这里处理，可以分情况讨论
     * 关闭计时器
     * 发送消息
     * 判断是否结束，若结束调用结束函数
     * 否则选择下一个人
     */
    public void doHandleAction(int index,Hand hand){

        if(hand.type.equals(HandType.PASS.type)){
            if(getNextIndex(index).equals(curMaxIndex)){
                turnEnd();
            }
        }
        else{
            curMaxType = HandType.valueOf(hand.type);
            curMaxIndex = index;
            curMaxLevel = hand.level;
            playAHand(hand,index);
        }
        sendAllInfo(index,hand);

        if(isFinish()){
            epiEnd();
            return;
        }
        curIndex = getNextIndex(curIndex);
        toPlay(curIndex);

    }

    private void sendAllInfo(int index, Hand hand) {
        Gson gson = new Gson();
        String json  = gson.toJson(new infoJO("info",index,hand));
        for(Session s: all){
            s.sendText(json);
        }
    }


    /**
     * 二级函数入口，由一级函数判断调用
     */
    /**
     * 一次牌权结束
     */
    public void turnEnd(){
        curIndex = curMaxIndex;
        curMaxLevel = null;
        curMaxType = null;
        curMaxIndex = null;
    }

    /**
     * 一小局结束
     */
    public void epiEnd(){
        this.gameStage = GameStage.OVER;
        for(int i=0;i<4;i++){
            if(rest[i]!=0){
                finishOne(i);
            }
            ready[i] = false;
        }
        int levelUp = getLevelUp();
        if(updateLevel(levelUp)){
            Gson gson = new Gson();
            String json = gson.toJson(new overJO("epiOver",this.finResult));
            for(Session s:all){
                s.sendText(json);
            }
        }
        else{
            gameEnd();
        }

    }

    private boolean updateLevel(int levelUp) {
        int winner = finResult[0];
        int friend = (winner+2)%4;
        if(rank[winner]==1&&levelUp>=2){
            return false;
        }else if(rank[winner]==1){
            ;
        }
        else{
            rank[winner] = rank[winner]+levelUp>=14?1:rank[winner]+levelUp;
            rank[friend] = rank[friend]+levelUp>=14?1:rank[friend]+levelUp;
        }
        if((rank[upStage]==1&&winner%2!=upStage)||(rank[upStage]==1&&finResult[3]==friend)){
            Afailure[upStage]++;
            if(Afailure[upStage]==3){
                rank[upStage] = 2;
                rank[upStage+2] = 2;
                Afailure[upStage] = 0;
            }
        }
        curRank = rank[winner];
        return true;
    }

    private int getLevelUp() {
        int levelUp = 0;
        if((finResult[0]+finResult[1]) % 2 == 0){
            levelUp = 3;
        }
        else if((finResult[0]+finResult[2])%2==0){
            levelUp = 2;
        }
        else if((finResult[0]+finResult[3])%2==0){
            levelUp = 1;
        }
        return levelUp;

    }


    /**
     * 过A结束
     * 完全结束
     * 销毁房间在 manager
     */
    public void gameEnd(){
        Gson gson = new Gson();
        String json = gson.toJson(new allOverJO("AllOver",this.finResult));
        for(Session s:all){
            s.sendText(json);
        }
    }

    /**
     * 在此前需要判断是否合法
     * 真正打出一手牌
     * @param hand 一手牌
     * @param index 打出这手牌的坐标
     */
    public void playAHand(Hand hand,int index){
        int[] cards = hand.getCards();
        ArrayList<Integer> arr = this.cards[index];
        for (int card : cards) {
            arr.removeIf(t -> t == card);
        }
        rest[curIndex] = this.cards[curIndex].size();
        if(rest[curIndex]==0){
            finishOne(curIndex);
        }
    }

    private void finishOne(int i) {
        this.finResult[fIndex] = i;
        fIndex++;
    }

    /**
     * 随机出牌
     */
    public void ramdomAhand(int index){
        int actIndex = random.nextInt(actionList.get(index).length);
        doHandleAction(index,actionList.get(index)[actIndex]);
    }

    /**
     * 给客户端发送出牌消息后
     * toPlay调用
     */
    public void waitForPlay() {
        timer.schedule(new TimerTask() {
            public void run() {
                if(curMaxIndex!=null)doHandleAction(curIndex,new Hand(HandType.PASS.type,new int[]{},0));
                else ramdomAhand(curIndex);
            }
        }, 0, timeCountDown* 1000L+2000);
    }

    /**
     * 辅助函数
     * @return
     */
    public int[] getHandCards(int index) {
        int[] ans = new int[index];
        ArrayList<Integer> arr =this.cards[index];
        for(int i=0;i<arr.size();i++){
            ans[i] = arr.get(i);
        }
        return ans;
    }

    public Hand[] getActionList(int index) {
        List<HandType> l = this.getHandType(this.curMaxType);
        ArrayList<Hand> ans = new ArrayList<>();
        for(HandType type:l){
            int[] cardt = new int[cards[curIndex].size()];
            for(int i=0;i<cards[curIndex].size();i++){
                cardt[i] = cards[curIndex].get(i);
            }
            Picker.pick(type,curMaxLevel,10000,curMaxType,ans,cardt,curRank);
        }
        Hand[] ret = new Hand[ans.size()];
        this.actionList.put(index,ret);
        return ans.toArray(ret);
    }

    public List<HandType> getHandType(HandType type){
        ArrayList<HandType> ans = new ArrayList<>();
        for(HandType t:HandType.values()){
            if(t.typelevel>= type.typelevel){
                ans.add(t);
            }
        }
        return ans;
    }

    public int getCurRank() {
        return this.curRank;
    }

    public int[] getRest() {
        int[] rest = new int[4];
        for(int i=0;i<4;i++){
            rest[i] = cards[i].size();
        }
        return rest;
    }

    public void handleRes(AIRes res) {
        int actIndex = res.getActIndex();
        int index = res.getIndex();
        Hand hand = actionList.get(index)[actIndex];
        if(gameStage==GameStage.PLAY){
            doHandleAction(index,hand);
        }
        else if(gameStage==GameStage.TRIBUTE)getTribute(hand,index);
        else if(gameStage==GameStage.BACK)getBack(hand,index);

    }

    public void handleRes(HumanRes res){
        int index = res.getIndex();
        if(gameStage==GameStage.PLAY)doHandleAction(index,res.getHand());
        else if(gameStage==GameStage.TRIBUTE)getTribute(res.getHand(),index);
        else if(gameStage==GameStage.BACK)getBack(res.getHand(),index);
    }
    private Integer getNextIndex(Integer index) {
        index = (index + 1) % 4;
        while(rest[index]==0){
            index = (index + 1) % 4;
        }
        return index;
    }

    private boolean isFinish() {
        if(rest[0] + rest[2]==0){
            return true;
        }
        return rest[1] + rest[3] == 0;
    }

    public String getCurMaxType() {
        return this.curMaxType.type;
    }

    public int getCurLevel() {
        return this.curMaxLevel;
    }

    public int getCurMaxIndex() {
        return this.curMaxIndex;
    }

    public Integer getCurIndex() {
        return curIndex;
    }
}
